package com.neusoft.bizcore.auth.service.impl;

import java.util.List;
import java.util.stream.Collectors;

import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import com.neusoft.bizcore.auth.converter.AuthRoleConverter;
import com.neusoft.bizcore.auth.dto.AuthRoleCreateUpdateDTO;
import com.neusoft.bizcore.auth.dto.AuthRoleDTO;
import com.neusoft.bizcore.auth.model.AuthPermission;
import com.neusoft.bizcore.auth.model.AuthRole;
import com.neusoft.bizcore.auth.model.AuthUser;
import com.neusoft.bizcore.auth.repository.AuthPermissionRepository;
import com.neusoft.bizcore.auth.repository.AuthRoleRepository;
import com.neusoft.bizcore.auth.repository.AuthUserRepository;
import com.neusoft.bizcore.auth.service.AuthRoleService;
import com.neusoft.bizcore.web.constant.ExceptionConstants;
import com.neusoft.bizcore.web.exception.UnitedException;
import com.neusoft.bizcore.web.support.Searchable;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@Transactional
public class AuthRoleServiceImpl implements AuthRoleService {

    @Autowired
    private AuthRoleRepository authRoleRepository;
    @Autowired
    private AuthRoleConverter authRoleConverter;
    @Autowired
    private AuthUserRepository authUserRepository;
    @Autowired
    private AuthPermissionRepository authPermissionRepository;

    @Override
    public List<AuthRoleDTO> findAll() {
        final List<AuthRole> models = this.authRoleRepository.findAll();
        final List<AuthRoleDTO> dtos = this.authRoleConverter.toListDTO(models);
        return dtos;
    }

    @Override
    public List<AuthRoleDTO> search(final Searchable searchable) {
        final List<AuthRole> models = this.authRoleRepository.search(searchable);
        return this.authRoleConverter.toListDTO(models);
    }

    @Override
    public Page<AuthRoleDTO> search(final Pageable pageable, final Searchable searchable) {
        final Page<AuthRole> models = this.authRoleRepository.search(pageable, searchable);
        return this.authRoleConverter.toPageDTO(models);
    }

    @Override
    public AuthRoleDTO create(final AuthRoleCreateUpdateDTO dto) {
        final AuthRole model = this.authRoleConverter.toModel(dto);
        this.authRoleRepository.save(model);
        return this.authRoleConverter.toDTO(model);
    }

    @Override
    public AuthRoleDTO create(final AuthRoleDTO dto) {
        final AuthRole model = this.authRoleConverter.toModel(dto);
        this.authRoleRepository.save(model);
        return this.authRoleConverter.toDTO(model);
    }

    @Override
    public AuthRoleDTO details(final Long id) {
        final AuthRole model;
        try {
            model = this.authRoleRepository.getOne(id);
        } catch (final EntityNotFoundException e) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, id);
        }
        return this.authRoleConverter.toDTO(model);
    }

    @Override
    public void delete(final Long id) {
        try {
            final AuthRole model = this.authRoleRepository.getOne(id);
            this.authRoleRepository.delete(model);
        } catch (final EntityNotFoundException e) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, id);
        }
    }

    @Override
    public AuthRoleDTO update(final Long id, final AuthRoleCreateUpdateDTO dto) {
        AuthRole model;
        try {
            model = this.authRoleRepository.getOne(id);
        } catch (final EntityNotFoundException e) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, id);
        }

        dto.setId(model.getId());
        model = this.authRoleConverter.toModel(dto);
        this.authRoleRepository.save(model);
        return this.authRoleConverter.toDTO(model);
    }

    @Override
    public AuthRoleDTO bindUsers(final Long id, final List<Long> users) {
        return this.bingUsers(id, users, false);
    }

    @Override
    public AuthRoleDTO addUsers(final Long id, final List<Long> users) {
        return this.bingUsers(id, users, true);
    }

    @Override
    public AuthRoleDTO removeUsers(final Long id, final List<Long> users) {
        AuthRole model;
        try {
            model = this.authRoleRepository.getOne(id);
        } catch (final EntityNotFoundException e) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, id);
        }

        final List<AuthUser> willRemovedUsers =
                model.getUsers().stream().filter(u -> users.contains(u.getId())).collect(Collectors.toList());

        model.getUsers().removeAll(willRemovedUsers);
        this.authRoleRepository.save(model);

        return this.authRoleConverter.toDTO(model);
    }

    private AuthRoleDTO bingUsers(final Long id, final List<Long> users, final boolean add) {
        AuthRole model;
        try {
            model = this.authRoleRepository.getOne(id);
        } catch (final EntityNotFoundException e) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, id);
        }
        if (!add) {
            model.getUsers().clear();
        }
        users.stream().map(i -> {
            AuthUser authUser = null;
            try {
                if (!model.getUsers().stream().map(AuthUser::getId).collect(Collectors.toList()).contains(i)) {
                    authUser = this.authUserRepository.getOne(i);
                }
            } catch (final EntityNotFoundException e) {
                AuthRoleServiceImpl.log.warn("permission {} is not found.", i);
            }
            return authUser;
        }).filter(u -> null != u).forEach(model.getUsers()::add);

        this.authRoleRepository.save(model);

        return this.authRoleConverter.toDTO(model);
    }

    @Override
    public AuthRoleDTO bindPermissions(final Long id, final List<Long> permissions) {
        return this.bingPermissions(id, permissions, false);
    }

    @Override
    public AuthRoleDTO addPermissions(final Long id, final List<Long> permissions) {
        return this.bingPermissions(id, permissions, true);
    }

    @Override
    public AuthRoleDTO removePermissions(final Long id, final List<Long> permissions) {
        AuthRole model;
        try {
            model = this.authRoleRepository.getOne(id);
        } catch (final EntityNotFoundException e) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, id);
        }

        final List<AuthPermission> willRemovedPermissions =
                model.getPermissions().stream().filter(u -> permissions.contains(u.getId()))
                        .collect(Collectors.toList());

        model.getPermissions().removeAll(willRemovedPermissions);
        this.authRoleRepository.save(model);

        return this.authRoleConverter.toDTO(model);
    }

    private AuthRoleDTO bingPermissions(final Long id, final List<Long> permissions, final boolean add) {
        AuthRole model;
        try {
            model = this.authRoleRepository.getOne(id);
        } catch (final EntityNotFoundException e) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, id);
        }
        if (!add) {
            model.getPermissions().clear();
        }
        permissions.stream().map(i -> {
            AuthPermission authPermission = null;
            try {
                if (!model.getPermissions().stream().map(AuthPermission::getId).collect(Collectors.toList())
                        .contains(i)) {
                    authPermission = this.authPermissionRepository.getOne(i);
                }
            } catch (final EntityNotFoundException e) {
                AuthRoleServiceImpl.log.warn("permission {} is not found.", i);
            }
            return authPermission;
        }).filter(u -> null != u).forEach(model.getPermissions()::add);

        this.authRoleRepository.save(model);

        return this.authRoleConverter.toDTO(model);
    }

    @Override
    public AuthRoleDTO detailsByCode(final String code) {

        final AuthRole model = this.authRoleRepository.findByCode(code);

        if (null == model) {
            throw new UnitedException(ExceptionConstants.ENTITY_NOT_FOUND, code);
        }

        return this.authRoleConverter.toDTO(model);
    }
}
